package manager.connection;

import java.awt.print.Printable;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.URI;
import java.net.URISyntaxException;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

import org.apache.http.client.utils.URIBuilder;
import org.apache.log4j.Logger;
import org.springframework.beans.BeansException;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

import com.aliyuncs.dysmsapi.model.v20170525.SendSmsResponse;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;

import advice.ExceptionManager;

import database.DBService;
import database.pojo.MachineInfo;
import database.pojo.UserInfo;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelFuture;
import manager.connection.state.MachineStatus;
import manager.connection.type.ConnectionInfo;
import manager.connection.type.MachineActivateInfo;
import manager.http.HttpClient;
import manager.pay.GetPayResult;
import manager.pay.type.EndTimeInfo;
import manager.qrcode.QRCodeManager;
import manager.rank.InsertToRedis;
import manager.songlist.Songlist;
import manager.songlist.SonglistControl;
import net.socket.server.SocketServer;
import net.socket.server.message.MessageManager;
import net.socket.server.notification.GameServerWechatLoginNTF;
import net.socket.server.notification.MachineIDLeavedNTF;
import net.socket.server.protocal.PROTOID;
import net.socket.server.request.IRequest;
import net.socket.server.request.MachineLoginRequest;
import net.socket.server.request.MachineTimeVerificationRequest;
import net.socket.server.request.PlayerLogoutRequest;
import net.socket.server.response.MachineLoginResponse;
import net.socket.server.response.MachineTimeVerificationResponse;
import net.socket.server.response.PlayerLogoutResponse;
import observable.subject.MessageCenter;
import service.IUserLogin;
import service.message.QRCodeEvent;
import service.message.WeixinUserInfo;
import util.MyFactory;
import util.ResultTypeList;
import net.socket.server.request.testLogin;
@Component("ClientConnectionManager")
public class ClientConnectionManager implements IUserLogin{
	
	private static Gson gson = new Gson(); 
	// first param(int) is the machine id
	private static Map<Integer, ConnectionInfo> connections = new HashMap<Integer, ConnectionInfo>();
	private static Logger logger = Logger.getLogger(ExceptionManager.class);
	
	
	//public volatile static Map<String, Integer> vipPoints = new HashMap<String,Integer>();
	public  static Map<String, Double> allRecharge = new HashMap<String,Double>();
	public  static Map<String, Double> allConsume = new HashMap<String,Double>();
	
	
	//spring计划任务，定时清理无效的连接,每五秒一次
	@Scheduled(cron = "0/60 * * * * *")
	public void cleanUp(){
		long nowTime =0;
			Iterator<Map.Entry<Integer, ConnectionInfo>> it = connections.entrySet().iterator();
			while(it.hasNext()){
				nowTime =System.currentTimeMillis()/1000;
	            Map.Entry<Integer, ConnectionInfo> entry = it.next();  
	            if(!entry.getValue().getCtx().channel().isActive()){
					//System.out.println("remove connection of machine id: " + entry.getKey());
	            	logger.info("remove connection of machine id:"+entry.getKey());
					synchronized(connections) {
						it.remove();
					}
				}else if (nowTime-entry.getValue().getPingTime()>60) {
					logger.info("remove pingTime >60 connection of machine id :"+entry);
	            	//System.out.println("remove pingTime >60 connection of machine id :"+entry.getKey());
	            	entry.getValue().getCtx().close();
	            	MyFactory.context.getBean("DBService", DBService.class)
	        		.updateMidOnline(entry.getKey(),MachineStatus.MACHINE_NOT_ON_LINE);
	            	synchronized(connections) {
	            		it.remove();
	            	}
	            }
	        }
	}
	
	//初始化，注册observer到message center(->是java1.8的特性，lambda表达式：x -> x+1,返回x+1）
	public void init(){
		MessageCenter mc = MyFactory.context.getBean("MessageCenter", MessageCenter.class);
		mc.register(PROTOID.LOGIN_GAME_SVR_REQ, (int cmd, IRequest request)->handleMachineLogin(cmd, request));
		mc.register(PROTOID.MACHINE_TIME_VERIFICATION_REQ, (int cmd, IRequest request)->handleMachineTimeVerification(cmd, request));
		//mc.register(PROTOID.TEST_LOGIN, (int cmd, IRequest request)->testLogin(cmd, request));
		
		
	}
	
	public int handleMachineTimeVerification(int cmd, IRequest request) throws IllegalArgumentException, IllegalAccessException, JsonProcessingException{
		setPingTmie(request.getMachineId());
		MachineTimeVerificationRequest req = (MachineTimeVerificationRequest) request;
		MachineTimeVerificationResponse rsp = new MachineTimeVerificationResponse();
		rsp.setMachineTime(req.getMachineTime());
		rsp.setResult(ResultTypeList.SUCCESS);
		rsp.setServerTime(System.currentTimeMillis());
		ChannelFuture cf = sendResponse(PROTOID.MACHINE_TIME_VERIFICATION_RSP, req.getMachineId(), rsp);
		if(cf == null){
			return ResultTypeList.NO_CONNECTION_FOUND;
		}
		else return rsp.getResult();
	}
	
	
	//处理机器连接请求
	public int handleMachineLogin(int cmd, IRequest request) throws BeansException, SQLException, IllegalArgumentException, IllegalAccessException, URISyntaxException, IOException{
		// record the connection information
		MachineLoginRequest req = (MachineLoginRequest) request;
		MachineLoginResponse rsp = new MachineLoginResponse();
		if(req.getMachineId()==-1 ||req.getCtx()==null || "".equals(req.getPrivateIp())||req.getPrivatePort()==-1) {
			rsp.setResult(ResultTypeList.NULL_ARGS);
			sendResponse(PROTOID.LOGIN_GAME_SVR_RSP, req.getMachineId(), rsp);
			return ResultTypeList.NULL_ARGS;
		}
		ConnectionInfo connInfo = new ConnectionInfo();
		connInfo.setMachineId(req.getMachineId());
        connInfo.setCtx(req.getCtx());
        connInfo.setPrivateIp(req.getPrivateIp());
        connInfo.setPrivatePort(req.getPrivatePort());
        // get remote address and port
        if(null ==req.getCtx()) {
        	 connInfo.setPublicIp("");
             connInfo.setPublicPort(0);
        }else {
        	InetSocketAddress address = (InetSocketAddress) req.getCtx().channel().remoteAddress();
            connInfo.setPublicIp(address.getAddress().getHostAddress());
            connInfo.setPublicPort(address.getPort());
		}
     
        removeExistMid(req.getMachineId(),connInfo);
        MachineInfo minfo =  MyFactory.context.getBean("DBService",DBService.class).selectMachineInfoById(req.getMachineId());       
        String QrcodeUrl= MyFactory.context.getBean("QRCodeManager",QRCodeManager.class).getQRCode(req.getMachineId());
        
        //String QrcodeUrl = "111111";
        rsp.setEndTime(minfo.getEndTime());
        rsp.setQrcodeUrl(QrcodeUrl);    
        rsp.setResult(ResultTypeList.SUCCESS);
        rsp.setPublicIp(connInfo.getPublicIp());
        //rsp.setPublicIp("192.168.1.6");
        
        rsp.setPublicPort(connInfo.getPublicPort());
       
		//judgeMidActivate(req.getMachineId(),rsp);
		ChannelFuture cf = sendResponse(PROTOID.LOGIN_GAME_SVR_RSP, req.getMachineId(), rsp);
		Songlist rsp2 = new Songlist();
		Gson gson = new Gson();
		ArrayList<String> songlist=(ArrayList<String>) MyFactory.context.getBean("InsertToRedis",InsertToRedis.class).cleanSonglistKey(req.getMachineId(), -2);
		String songlistString = gson.toJson(songlist);
		rsp2.setSonglist(songlistString);
		rsp2.setLength(songlist.size());
		sendResponse(PROTOID.NOTIFY_UPDATE_SONGLIST_REQ,req.getMachineId(),rsp2); // 通知歌单更新
	if(cf == null){
			return ResultTypeList.NO_CONNECTION_FOUND;
		}		
		else {			
			return rsp.getResult();
		}
	}
	

	private void judgeMidActivate(int machineId, MachineLoginResponse rsp) {
		String httpResult;
		try {
			URI getUri = new URIBuilder("http://"+SocketServer.adress+":30666/MachineActiveStatus/"+Integer.toString(machineId))	
					.build();
			httpResult = MyFactory.context.getBean("HttpClient", HttpClient.class).httpGet(getUri);
			if(httpResult ==null) {
				judgeActiveteByself(machineId,rsp);	
				return;		
			}
		} catch (BeansException |URISyntaxException| IOException e) {
			judgeActiveteByself(machineId, rsp);
			return;
		}
		MachineActivateInfo mInfo= gson.fromJson(httpResult, new TypeToken<MachineActivateInfo>(){}.getType()); 
		
		if(mInfo.getSTATUS() ==1) {//php那边0表示未注册，vod这边0表示成功
			rsp.setStatus(MachineStatus.NORMAL);
		}else if (mInfo.getSTATUS() ==0) {
			rsp.setStatus(MachineStatus.MACHINE_NO_ACTIVATE);	
		}else {
			rsp.setStatus(MachineStatus.NO_KOWN_STATUS);
		}
		if(mInfo.getActiveUrl() !=null) {
			rsp.setMusicpro_mid(mInfo.getActiveUrl().getMusicpro_mid());
		}
	}
	

	private void judgeActiveteByself(int machineId, MachineLoginResponse rsp) {
		boolean isActivate =MyFactory.context.getBean("DBService", DBService.class).midISActivate(machineId);
		if(isActivate) {
			rsp.setStatus(MachineStatus.NORMAL);
		}else {
			rsp.setStatus(MachineStatus.MACHINE_NO_ACTIVATE);
		}
		rsp.setMusicpro_mid(machineId);
		
	}

	
	
	//向某一已连接的主机发送消息
	public ChannelFuture sendResponse(int cmd, int machineId, Object rsp) throws IllegalArgumentException, IllegalAccessException, JsonProcessingException{
		MessageManager messageManager = MyFactory.context.getBean("MessageManager", MessageManager.class);
		ByteBuf buf = messageManager.generateCommandBuf(cmd, machineId, rsp);
		ConnectionInfo connInfo = this.getConnectionInfo(machineId);
		if(connInfo != null){
			while (connInfo.getCtx().channel().isActive()) {
				if(connInfo.getCtx().channel().isWritable()){
					return  connInfo.getCtx().writeAndFlush(buf);
				}
			}
			System.out.println("sendResponse channel status not active machineId: "+machineId);
			logger.info("sendResponse channel status not active machineId: "+machineId);
			return null;
		}
		else {
			logger.info("no connection for machine " + machineId);
			System.out.println("no connection for machine " + machineId);
			return null;
		}
		
		//return null;
	}
	
	public String getOpenIdByMachine(int machineId){
		if(connections.containsKey(machineId))
			return getConnectionInfo(machineId).getLoginedOpenid();
		return null;
	}
	
	public ConnectionInfo addConnection(int machineName, ConnectionInfo connInfo){
		
		//MyFactory.context.getBean("DBService", DBService.class)
		//.updateMidOnline(machineName,MachineStatus.MACHINE_ON_LINE);
		
		long pingTime = System.currentTimeMillis()/1000;
		connInfo.setPingTime(pingTime);
		synchronized (connections) {
			
			return connections.put(machineName, connInfo);
		}
	}
	
	public void setPingTmie(int machineName) {
		long pingTime = System.currentTimeMillis()/1000;
		if(connections.containsKey(machineName))
			connections.get(machineName).setPingTime(pingTime);
	}
	
	public ConnectionInfo removeConnection(int machineName){
		MyFactory.context.getBean("DBService", DBService.class)
		.updateMidOnline(machineName,MachineStatus.MACHINE_NOT_ON_LINE);
		synchronized (connections) {
			return connections.remove(machineName);
		}
	}
	
	public ConnectionInfo getConnectionInfo(int machineName){	
				return connections.get(machineName);						
	}
	
	public boolean removeExistMid(int machineId, ConnectionInfo connInfo) throws IllegalArgumentException, IllegalAccessException, JsonProcessingException {	
		
		if(null!=getConnectionInfo(machineId)&&machineId==getConnectionInfo(machineId).getMachineId()) {
			
			MachineIDLeavedNTF rsp = new MachineIDLeavedNTF();
			sendResponse(PROTOID.NTF_MACHINELEAVED, machineId, rsp);
			getConnectionInfo(machineId).getCtx().close();
			//System.out.println("closeExistMid :"+machineId);
			logger.info("closeExistMid :"+machineId);
		}
		
		addConnection(machineId, connInfo);
		return true;		
	}


	@Override
	public String login(int protoId, String baseInfo, String additionalInfo) throws Exception {
		// TODO Auto-generated method stub
		ObjectMapper mapper = new ObjectMapper();
		QRCodeEvent event = mapper.readValue(baseInfo, QRCodeEvent.class);
		WeixinUserInfo wUserInfo = mapper.readValue(additionalInfo, WeixinUserInfo.class);
		int machineId = Integer.valueOf(event.getEventKey());
		
		
		GameServerWechatLoginNTF ntf = new GameServerWechatLoginNTF();
		ntf.setPlayerInfo(wUserInfo);
		
		ChannelFuture cf = sendResponse(PROTOID.GAME_SVR_PLAYER_LOGIN_NTF, machineId, ntf);
		
		return null;
	}
}
	
	